﻿using System;
using System.Collections.Generic;
using NUnit.Framework;

namespace CSSpec.Tests
{
    [TestFixture]
    public class NUnitExtensionsFixture : CSSpecFixture<NUnitExtensionsFixture.State>
    {
        [Test]
        public void ShouldEqual_TwoObjectsThatAreEqualOfDifferentTypes()
        {
            Given(x => x.TwoObjectsThatAreEqualOfDifferentTypes())
                .When(x => x.ShouldEqualIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldEqual_TwoObjectsThatAreNotEqual()
        {
            Given(x => x.TwoObjectsThatAreNotEqual())
                .When(x => x.ShouldEqualIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldNotEqual_TwoObjectsThatAreNotEqual()
        {
            Given(x => x.TwoObjectsThatAreNotEqual())
                .When(x => x.ShouldNotEqualIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldNotEqual_TwoObjectsThatAreEqual()
        {
            Given(x => x.TwoObjectsThatAreEqual())
                .When(x => x.ShouldNotEqualIsCalled())
                .Then(x => x.TheTestShouldFail());
        } 
        [Test]
        public void ShouldEqualOne_AnObjectThatEqualsOne()
        {
            Given(x => x.AnObjectThatEqualsOne())
                .When(x => x.ShouldEqualOneIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldEqualOne_AnObjectThatDoesNotEqualOne()
        {
            Given(x => x.AnObjectThatEqualsZero())
                .When(x => x.ShouldEqualOneIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldEqualZero_AnObjectThatEqualsZero()
        {
            Given(x => x.AnObjectThatEqualsZero())
                .When(x => x.ShouldEqualZeroIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldEqualZero_AnObjectThatDoesNotEqualZero()
        {
            Given(x => x.AnObjectThatEqualsOne())
                .When(x => x.ShouldEqualZeroIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldBeTrue_AnObjectThatIsTrue()
        {
            Given(x => x.AnObjectThatIsTrue())
                .When(x => x.ShouldBeTrueIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldBeTrue_AnObjectThatIsFalse()
        {
            Given(x => x.AnObjectThatIsFalse())
                .When(x => x.ShouldBeTrueIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldBeFalse_AnObjectThatIsTrue()
        {
            Given(x => x.AnObjectThatIsTrue())
                .When(x => x.ShouldBeFalseIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldBeFalse_AnObjectThatIsFalse()
        {
            Given(x => x.AnObjectThatIsFalse())
                .When(x => x.ShouldBeFalseIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveNullProperty_AnObjectWithANullProperty()
        {
            Given(x => x.AnObjectWithANullProperty())
                .When(x => x.ShouldHaveNullPropertyIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveNullProperty_AnObjectWithANonNullProperty()
        {
            Given(x => x.AnObjectWithANonNullProperty())
                .When(x => x.ShouldHaveNullPropertyIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldNotHaveNullProperty_AnObjectWithANullProperty()
        {
            Given(x => x.AnObjectWithANullProperty())
                .When(x => x.ShouldNotHaveNullPropertyIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldNotHaveNullProperty_AnObjectWithANonNullProperty()
        {
            Given(x => x.AnObjectWithANonNullProperty())
                .When(x => x.ShouldNotHaveNullPropertyIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldBeOfType_TwoObjectsOfTheSameType()
        {
            Given(x => x.TwoObjectsThatAreEqualOfTheSameType())
                .When(x => x.ShouldBeOfTypeIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldBeOfType_TwoObjectsOfDifferentTypes()
        {
            Given(x => x.TwoObjectsThatAreEqualOfDifferentTypes())
                .When(x => x.ShouldBeOfTypeIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldHaveTheSameReferenceAs_TwoObjectsThatAreEqual()
        {
            Given(x => x.TwoObjectsThatAreEqual())
                .When(x => x.ShouldHaveTheSameReferenceAsIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveTheSameReferenceAs_TwoObjectsThatAreEqualButHaveDifferentReferences()
        {
            Given(x => x.TwoObjectsThatAreEqualButHaveDifferentReferences())
                .When(x => x.ShouldHaveTheSameReferenceAsIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldHaveTheSameReferenceAs_TwoObjectsThatAreNotEqual()
        {
            Given(x => x.TwoObjectsThatAreNotEqual())
                .When(x => x.ShouldHaveTheSameReferenceAsIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldBeGreaterThan_AnObectIsLessThan()
        {
            Given(x => x.AnObectIsLessThan())
                .When(x => x.ShouldBeGreaterThanIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldBeLessThan_AnObectIsLessThan()
        {
            Given(x => x.AnObectIsLessThan())
                .When(x => x.ShouldBeLessThanIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldBeLessThan_AnObectIsGreaterThan()
        {
            Given(x => x.AnObectIsGreater())
                .When(x => x.ShouldBeLessThanIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldNotBeNull_ANonNullObject()
        {
            Given(x => x.TwoObjectsThatAreEqual())
                .When(x => x.ShouldNotBeNullIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldNotBeNull_ANullObject()
        {
            Given(x => x.ANullObject())
                .When(x => x.ShouldNotBeNullIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldHaveOneItem_ACollectionWithOneItem()
        {
            Given(x => x.ACollectionWithOneItem())
                .When(x => x.ShouldHaveOneItemIsCalled())
                .Then(x => x.TheTestShouldPass()); 
        }
        [Test]
        public void ShouldHaveOneItem_ACollectionWithMoreThanOneItem()
        {
            Given(x => x.ACollectionWithMoreThanOneItem())
                .When(x => x.ShouldHaveOneItemIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldHaveOneItem_AnEmptyCollection()
        {
            Given(x => x.AnEmpyColection())
                .When(x => x.ShouldHaveOneItemIsCalled())
                .Then(x => x.TheTestShouldFail());
        }

        [Test]
        public void ShouldHaveOneItemWhere_HasAMatch()
        {
            Given(x => x.ACollectionWithMoreThanOneItem())
                .When(x => x.ShouldHaveOneItemWhereIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveOneItemWhere_DoesNotHaveAMatch()
        {
            Given(x => x.ACollectionWithMoreThanOneItem())
                .When(x => x.ShouldHaveOneItemWhereIsCalledAndThereIsNoMatch())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldHaveAtLeastOneItem_HasOneItem()
        {
            Given(x => x.ACollectionWithOneItem())
                .When(x => x.ShouldHaveAtLeastOneItemIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveAtLeastOneItem_HasZeroItems()
        {
            Given(x => x.AnEmpyColection())
                .When(x => x.ShouldHaveAtLeastOneItemIsCalled())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldHaveAtLeastOneItem_HasMoreThanOneItem()
        {
            Given(x => x.ACollectionWithMoreThanOneItem())
                .When(x => x.ShouldHaveAtLeastOneItemIsCalled())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveCount_MoreItemsThanCount()
        { 
            Given(x => x.ACollectionWithMoreThanOneItem())
                .When(x => x.ShouldHaveCountIsCalledToVerifyACountOfOne())
                .Then(x => x.TheTestShouldFail());
        }  
        [Test]
        public void ShouldHaveCount_MatchesCount()
        {
            Given(x => x.ACollectionWithOneItem())
                .When(x => x.ShouldHaveCountIsCalledToVerifyACountOfOne())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveCount_LessThanCount()
        {
            Given(x => x.AnEmpyColection())
                .When(x => x.ShouldHaveCountIsCalledToVerifyACountOfOne())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldHaveCountWhere_OneMatch()
        {
            Given(x => x.ACollectionWithMoreThanOneItem())
                .When(x => x.ShouldHaveCountWhereIsCalledAndThereIsOneMatch())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveCountWhere_TwoMatches()
        {
            Given(x => x.ACollectionWithMoreThanOneItem())
                .When(x => x.ShouldHaveCountWhereIsCalledAndThereIsMoreThanOneMatch())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldHaveCountWhere_NoMatches()
        {
            Given(x => x.ACollectionWithMoreThanOneItem())
                .When(x => x.ShouldHaveCountWhereIsCalledAndThereAreNoMatches())
                .Then(x => x.TheTestShouldFail());
        }
        [Test]
        public void ShouldContain_Contains()
        {
            Given(x => x.AString())
                .When(x => x.ShouldContainIsCalledWithAMatchingString())
                .Then(x => x.TheTestShouldPass());
        }
        [Test]
        public void ShouldContain_DoesNotContain()
        {
            Given(x => x.AString())
                .When(x => x.ShouldContainIsCalledWithANonMatchingString())
                .Then(x => x.TheTestShouldFail());
        }

        public class State
        {
            private object objectOne;
            private object objectTwo;
            private bool failed;


            internal void TwoObjectsThatAreEqual()
            {
                objectOne = new object();
                objectTwo = objectOne;
            }
            internal void TwoObjectsThatAreEqualOfDifferentTypes()
            {
                objectOne = new ObjectA();
                objectTwo = new ObjectB(); 
            }
            public void TwoObjectsThatAreEqualButHaveDifferentReferences()
            {
                objectOne = new ObjectA();
                objectTwo = new ObjectA(); 
            }
            internal void TwoObjectsThatAreNotEqual()
            {
                objectOne = new ObjectA();
                objectTwo = new DoesNotEqualAorB(); 
            }
            internal void AnObjectThatEqualsOne()
            {
                objectOne = 1; 
            }
            public void AnObjectThatEqualsZero()
            {
                objectOne = 0; 
            }
            public void AnObjectThatIsTrue()
            {
                objectOne = true; 
            }
            public void AnObjectThatIsFalse()
            {
                objectOne = false; 
            }

            public void AnObjectWithANullProperty()
            {
                objectOne = new ObjectA {Name = null}; 
            }
            public void AnObjectWithANonNullProperty()
            {
                objectOne = new ObjectA { Name = "123" }; 
            }
            public void TwoObjectsThatAreEqualOfTheSameType()
            {
                objectOne = new ObjectA();
                objectTwo = new ObjectA(); 
            }
            public void AnObectIsGreater()
            {
                objectOne = 1;
                objectTwo = 1.1;
            }
            public void AnObectIsLessThan()
            {
                objectOne = 1;
                objectTwo = .9;
            }
            public void ANullObject()
            {
                objectOne = null;
            }
            public void ACollectionWithOneItem()
            {
                objectOne = new[] {1};
            }
            public void ACollectionWithMoreThanOneItem()
            {
                objectOne = new[] { 1, 0, 1 };
            }
            public void AnEmpyColection()
            {
                objectOne = new int[0];
            }
            public  void AString()
            {
                objectOne = "one_two";
            }

            internal void ShouldEqualIsCalled()
            {
                Try(() => objectOne.ShouldEqual(objectTwo)); 
            }
            public void ShouldNotEqualIsCalled()
            {
                Try(() =>objectOne.ShouldNotEqual(objectTwo)); 
            }
            internal void ShouldEqualOneIsCalled()
            {
                Try(() => objectOne.ShouldEqualOne()); 
            }
            public void ShouldEqualZeroIsCalled()
            {
                Try(() => objectOne.ShouldEqualZero()); 
            }
            public void ShouldBeTrueIsCalled()
            {
                Try(() => ((bool)objectOne).ShouldBeTrue()); 
            }
            public void ShouldBeFalseIsCalled()
            {
                Try(() => ((bool)objectOne).ShouldBeFalse()); 
            } 
            public void ShouldHaveNullPropertyIsCalled()
            {
                Try(() => ((ObjectA)objectOne).ShouldHaveNullProperty(x => x.Name)); 
            }
            public void ShouldNotHaveNullPropertyIsCalled()
            {
                Try(() => ((ObjectA)objectOne).ShouldNotHaveNullProperty(x => x.Name)); 
            }
            public void ShouldBeOfTypeIsCalled()
            {
                Try(() => objectOne.ShouldBeOfType(objectTwo.GetType())); 
            } 
            public void ShouldHaveTheSameReferenceAsIsCalled()
            {
                Try(() => objectOne.ShouldHaveTheSameReferenceAs(objectTwo)); 
            }
            public void ShouldBeGreaterThanIsCalled()
            {
                Try(() => ((double)objectTwo).ShouldBeGreaterThan((int)objectOne));
            }
            public void ShouldBeLessThanIsCalled()
            {
                Try(() => ((double)objectTwo).ShouldBeLessThan((int)objectOne));
            }
            public void ShouldNotBeNullIsCalled()
            {
                Try(() => objectOne.ShouldNotBeNull());
            }
            public void ShouldHaveOneItemIsCalled()
            {
                Try(() =>((IEnumerable<int>)objectOne).ShouldHaveOneItem());
            }
            public void ShouldHaveOneItemWhereIsCalled()
            { 
                Try(() => ((IEnumerable<int>)objectOne).ShouldHaveOneItemWhere(x => x == 0));
            }
            public void ShouldHaveOneItemWhereIsCalledAndThereIsNoMatch()
            {
                Try(() => ((IEnumerable<int>)objectOne).ShouldHaveOneItemWhere(x => x == 9));
            } 
            public void ShouldHaveCountWhereIsCalledAndThereIsOneMatch()
            {
                Try(() => ((IEnumerable<int>)objectOne).ShouldHaveCountWhere(1, x => x == 0));
            }
            public void ShouldHaveCountWhereIsCalledAndThereIsMoreThanOneMatch()
            {
                Try(() => ((IEnumerable<int>)objectOne).ShouldHaveCountWhere(2, x => x == 1));
            }
            public void ShouldHaveCountWhereIsCalledAndThereAreNoMatches()
            {
                Try(() => ((IEnumerable<int>)objectOne).ShouldHaveCountWhere(1, x => x == 9));
            }
            public void ShouldHaveAtLeastOneItemIsCalled()
            {
                Try(() => ((IEnumerable<int>)objectOne).ShouldHaveAtLeastOneItem());
            }
            public void ShouldHaveCountIsCalledToVerifyACountOfOne()
            {
                Try(() => ((IEnumerable<int>)objectOne).ShouldHaveCount(1));
            }
            public void ShouldContainIsCalledWithAMatchingString()
            {
                Try(() => ((string)objectOne).ShouldContain("one"));
            }
            public void ShouldContainIsCalledWithANonMatchingString()
            {
                Try(() => ((string)objectOne).ShouldContain("three"));
            }

            internal void TheTestShouldPass()
            {
                Assert.IsFalse(failed);
            }
            internal void TheTestShouldFail()
            {
                Assert.IsTrue(failed);
            }


            private void Try(Action test)
            {
                try
                {
                    test();
                    failed = false;
                }
                catch (AssertionException)
                {
                    failed = true;
                }
            }


        }
    }
    
}
